/*
 Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd.

 https://www.cocos.com/

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights to
 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 of the Software, and to permit persons to whom the Software is furnished to do so,
 subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
*/

import { assertIsNonNullable, assertIsTrue } from './utils/asserts';

/**
 * Tag to define the custom serialization method.
 * @internal
 */
export const serializeTag = Symbol('[[Serialize]]');

/*----------------------  read source code ----------------------
  这行代码定义了一个名为 serializeTag 的常量，并将其赋值为一个唯一的 Symbol 值。
  Symbol 是 JavaScript 中的一种原始数据类型，用于创建唯一的标识符。即使两个 Symbol 值具有相同的描述，它们也是不相等的。
  这使得 Symbol 特别适合用作对象属性的键，以避免命名冲突。底层理念是，使用 Symbol 可以确保属性名的唯一性，从而避免与其他属性发生冲突。
  在这个上下文中，serializeTag 被用作一个标签，用于标识一个自定义序列化方法。这个方法可能会被用于将对象的状态转换为一种可存储或传输的格式。
*/


/**
 * Tag to define the custom deserialization method.
 * @internal
 */
export const deserializeTag = Symbol('[[Deserialize]]');

export interface SerializationInput {
    /**
     * Reads a property from the input.
     * @param name Property name.
     * @returns The property's value, after deserialized.
     */
    readProperty(name: string): unknown;

    /**
     * Deserializes this object according to the original procedure.
     */
    readThis(): void;

    /**
      * Deserializes super according to the original procedure.
      */
    readSuper(): void;
}

export interface SerializationOutput {
    /**
     * Writes a property into output.
     * @param name Property name.
     * @param value Property value.
     */
    writeProperty(name: string, value: unknown): void;

    /**
     * Serialize this object according to the original procedure.
     */
    writeThis(): void;

    /**
     * Serialize super according to the original procedure.
     */
    writeSuper(): void;
}

export type SerializationContext = {
    /**
     * The main serializing asset or root node in the scene/prefab passed to the serialization procedure.
     */
    root: unknown;
    /**
     * True if the serialization procedure is targeting CCON.
     */
    toCCON: boolean;
    /**
     * Customized arguments passed to serialization procedure.
     */
    customArguments: Record<PropertyKey, unknown>;
};

/**
 * @engineInternal
 */
export type DeserializationContext = {
    /**
     * True if the deserialization procedure is deserializing from CCON.
     */
    fromCCON: boolean;
};

export interface CustomSerializable {
    [serializeTag](output: SerializationOutput, context: SerializationContext): void;
    [deserializeTag]?(input: SerializationInput, context: DeserializationContext): void;

    /*----------------------  read source code ----------------------
   这行代码定义了一个接口 CustomSerializable，它包含一个名为 serializeTag 的属性。
   这个属性是一个方法，接受两个参数：output 和 context。output 是一个 SerializationOutput 类型的对象，context 是一个 SerializationContext 类型的对象。
   这个方法的返回值是 void，表示它不返回任何值。
   这个接口的目的是定义一个自定义序列化的方法，用于将对象的状态转换为一种可存储或传输的格式。
   */
}

/**
 * Enables the custom to serialize/deserialize method only if the (de)serialize procedure is targeting CCON.
 * @deprecated since v3.5.0, this is an engine private interface that will be removed in the future.
 */
export const enableIfCCON: MethodDecorator = <T>(
    // eslint-disable-next-line @typescript-eslint/ban-types
    _target: Object,
    propertyKey: PropertyKey,
    descriptor: TypedPropertyDescriptor<T>,
): TypedPropertyDescriptor<T> | void => {
    const original = descriptor.value;
    assertIsNonNullable(original);
    if (propertyKey === serializeTag) {
        return {
            ...descriptor,
            value: function wrapSerialize(output: SerializationOutput, context: SerializationContext) {
                if (!context.toCCON) {
                    output.writeThis();
                } else {
                    (original as unknown as CustomSerializable[typeof serializeTag]).call(this, output, context);
                }
            },
        } as unknown as TypedPropertyDescriptor<T>;
        /*----------------------  read source code ----------------------
        这行代码使用了 JavaScript 的对象展开运算符（spread operator），将 descriptor 对象中的所有属性复制到新的对象中。
        descriptor 是方法装饰器参数之一，包含了被装饰方法的元数据（如 value、writable、enumerable、configurable 等）。

        通过 ...descriptor，可以在返回新的属性描述符时，保留原有的所有属性设置，只对需要修改的属性（如 value，即方法本身）进行覆盖。
        这种写法有助于确保装饰器不会意外丢失原方法的其他元信息，是实现装饰器时的常见且安全的做法。
        */
    } else {
        assertIsTrue(propertyKey === deserializeTag, '@enableIfCCON should be only applied to custom (de)serialize method');
        return {
            ...descriptor,
            value: function wrapDeserialize(input: SerializationInput, context: DeserializationContext) {
                if (!context.fromCCON) {
                    input.readThis();
                } else {
                    (original as unknown as NonNullable<CustomSerializable[typeof deserializeTag]>).call(this, input, context);
                }
            },
        } as unknown as TypedPropertyDescriptor<T>;
    }
};
